/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "render_mng.hh"
#include "ogl.hh"
#include "ogl_font.hh"
#include "event_mng.hh"
#include "render_obj.hh"
#include "render_obj_frame.hh"
#include "render_obj_camera.hh"
#include "render_params.hh"
#include "render_stats.hh"
#include "time_queue.hh"
#include <string.h>

// DEFINITIONS
#define INC_HIERARCHY        32
#define INC_FONTS            8
#define MAX_SELECT_GL_BUFFER 8192
#define MAX_DEPTH            0xffffffff
#define Z_DIFF               2048.0 // offset for all but polygon

// RENDER PASS NAMES
const char *render_pass_name[RENDER_PASS_LAST_ALL]=
{
    "precalc", "opaque", "blend", "hud", "last", "select",
};


CLASS_INIT(render_mng); // RENDER_MNG CLASS

render_mng::render_mng()
{
    CLASS_CTOR(render_mng);
    ctor();

    select_gl_buffer=(unsigned *)malloc(MAX_SELECT_GL_BUFFER*sizeof(unsigned));
}

render_mng::~render_mng()
{
    CLASS_DTOR(render_mng);
    dtor();

    free(select_gl_buffer);
}

void render_mng::ctor()
{
    // config
    refresh_full=true;
    refresh_min_time=5000;

    w=NULL;
    root=NULL;
    params=NULL;
    forced_render=false;
    pass=RENDER_PASS_LAST;
    pass_hud_aux=false;
    util::ctor_vector(f, n_f, max_f);

    // referenced objects
    obj_frame=NULL;
    obj_camera=NULL;

    // render hierarchy
    util::ctor_vector(hierarchy, n_hierarchy, max_hierarchy);

    // event time queue
    queue=NULL;

    // select buffer
    n_select_gl_buffer=0;
    i_select_gl_buffer=-1;
    util::ctor_vector(select_hierarchy, n_select_hierarchy, max_select_hierarchy);

    // events
    event=NULL;
    event_last=0;

    // stats
    stats_current=NULL;

    // action focus
    focus_action=0;
    focus_obj=NULL;

    // action params
    action_params=NULL;
    max_action_params=0;

    // calculations
    calc_all=0;
}

void render_mng::dtor()
{
    if(w) {w->decref(); w=NULL;}
    if(root) {root->decref(); root=NULL;}
    if(params) {params->decref(); params=NULL;}

    // render hierarchy
    util::dtor_vector(hierarchy, n_hierarchy, max_hierarchy);

    // select hierarchy
    util::dtor_vector(select_hierarchy, n_select_hierarchy, max_select_hierarchy);

    // event time queue
    if(queue)
    {
	delete queue;
	queue=NULL;
    }

    // events
    if(event) {event->decref(); event=NULL;}

    // stats
    if(stats_current) {stats_current->decref(); stats_current=NULL;}

    // fonts
    for(int i=0; i<n_f; i++) if(f[i]) f[i]->decref();
    util::dtor_vector(f, n_f, max_f);

    // action params
    if(action_params) action_params=realloc(action_params, 0);
}

bool render_mng::init(ogl_window *win, event_mng *event_)
{
    // window
    if(win) win->incref();
    if(w) w->decref();
    w=win;
    w->set_cursor(OGL_CURSOR_NONE);

    // event manager
    if(event_) event_->incref();
    if(event) event->decref();
    event=event_;

    // render params
    if(!params) params=new render_params;

    // event time queue
    if(!queue) queue=new time_queue;

    // init OpenGL
    w->set_gl_ctx();
    glReadBuffer(GL_BACK);
    glClearColor(0.4, 0.1, 0.0, 0.0);

    // fonts
    if(!f)
    {
	struct Fonts
	{
	    bool free_type;
	    const char *name;
	    float height;
	} fonts[]=
	  {
	      {false, "6x13",  0}, // RENDER_FONT_BITMAP
	      {true,  NULL,   32}, // RENDER_FONT_FT_DEFAULT
	      {false, NULL,   -1},
	  };
	for(int i=0; fonts[i].height>=0; i++)
	{
	    int j=util::inc_vector(f, n_f, max_f, INC_FONTS);
	    f[j]=new ogl_font;
	    if(fonts[i].free_type)
		f[j]->init(fonts[i].name, fonts[i].height);
	    else
		f[j]->init(fonts[i].name);
	}
    }

    // auxiliary buffers
    int n_aux;
    glGetIntegerv(GL_AUX_BUFFERS, &n_aux);
    if(n_aux>0) pass_hud_aux=true;
    else
    {
	pass_hud_aux=false;
	ERROR("Warning: No auxiliary openGL buffer");
    }

    // gl offset stuff
    glPolygonOffset(0.0/*factor*/, Z_DIFF/*units*/);
    glDisable(GL_POLYGON_OFFSET_LINE);
    glEnable(GL_POLYGON_OFFSET_FILL);
    glDisable(GL_POLYGON_OFFSET_POINT);

    // root object
    if(!root) root=new render_obj;
    if(!root->init(this)) ERROR_RX(false, "cannot init root render_obj");

    // register an event callback of em
    event->set_callback_events(OGL_MOUSE_BUTTON_EVENT | OGL_KEYBOARD_EVENT | OGL_ENTER_LEAVE_EVENT);
    event->set_callback((event_callback_type)event_callback_static, this);

    return true;
}

int render_mng::render()
{
    // events
    int usec=event->event_dispatcher();

    // window updates (due to expose events)
    int *update_coord;
    if((update_coord=event->update_required())) render_update_from_back(update_coord);

    // complete render
    forced_render=event->redraw_required();

    // refresh render event queue
    queue->refresh();

    // select render if necesary
    if(event->get_cursor_moved() && !focus_action)
    {
	select_render();
	usec=0;
    }

    // render only if necessary
    bool force_next_select_pass=false;
    if(root->get_forced_pass()<RENDER_PASS_LAST || forced_render)
    {
	if(forced_render) pass=RENDER_PASS_PRECALC; else pass=root->get_forced_pass();

	// forced pass of specified layers
	while(pass<RENDER_PASS_LAST)
	{
	    if(root->get_required_pass() & get_pass_mask())
	    {
		root->render(this);

		// check ogl errors
		int err;
		if((err=glGetError())!=GL_NO_ERROR) ERROR("OGL error: 0x%8x", err);
	    }
	    else if(root->get_forced_pass()==pass) root->inc_forced_pass();
	    inc_pass();
	}

	if(root->get_forced_pass()<RENDER_PASS_LAST)
	{
	    usec=0;
	    if(root->get_forced_pass()==RENDER_PASS_PRECALC) force_next_select_pass=true;
	}
    }

    // pass movement events
    if(event->get_cursor_moved())
    {
	event->set_event_last(OGL_MOUSE_MOVE_EVENT);
	event_callback();

	// detect movement events in next frame
	event->reset_cursor_moved();
    }

    // force next select pass
    if(force_next_select_pass) event->set_cursor_moved();

    // full refresh
    if(!refresh_full && !usec) usec=refresh_min_time;

    return usec;
}

bool render_mng::render_update_from_back(int *wcoor)
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glOrtho(0, w->get_width(), 0, w->get_height(), -1, 1);
    glViewport(0, 0, w->get_width(), w->get_height());
    if(pass_hud_aux) glReadBuffer(GL_AUX0);
    glDrawBuffer(GL_FRONT);
    glDisable(GL_DEPTH_TEST);
    if(wcoor)
    {
	// update indicated window box
	glRasterPos2i(wcoor[IS_WX], wcoor[IS_WY]);
	glCopyPixels(wcoor[IS_WX], wcoor[IS_WY], wcoor[IS_WW], wcoor[IS_WH], GL_COLOR);
    }
    else
    {
	// complete update
	glRasterPos2i(0, 0);
	glCopyPixels(0, 0, w->get_width(), w->get_height(), GL_COLOR);
    }
    glFlush();
    if(pass_hud_aux) glReadBuffer(GL_BACK);
    glDrawBuffer(GL_BACK);
    return true;
}

void render_mng::hierarchy_enter(render_obj *ro)
{
    int i=util::inc_vector(hierarchy, n_hierarchy, max_hierarchy, INC_HIERARCHY);
    hierarchy[i]=ro;
}

void render_mng::hierarchy_leave()
{
    if(!n_hierarchy) ERROR("internal: hierarchy");
    n_hierarchy--;
}

void render_mng::event_callback_static(render_mng *obj)
{
    obj->event_callback();
}

void render_mng::event_callback()
{
    if(select_hierarchy)
    {
	// set obj_frame from hierarchy
	for(int i=n_select_hierarchy-1; i>=0; i--)
	    if(select_hierarchy[i]->get_type()==IS_TYPE_FRAME)
	    {
		obj_frame=dynamic_cast<render_obj_frame*>(select_hierarchy[i]);
		break;
	    }
	// set obj_camera from hierarchy
	for(int i=n_select_hierarchy-1; i>=0; i--)
	    if(select_hierarchy[i]->get_type()==IS_TYPE_CAMERA)
	    {
		obj_camera=dynamic_cast<render_obj_camera*>(select_hierarchy[i]);
		break;
	    }

	// loop over all hierarchy selection from root down calling event
	event_last=0; // this is not a status change event
	for(int i=n_select_hierarchy-1; i>=0; i--)
	{
	    bool processed=false;

	    // focus exclusivity
	    if(event->get_event_last()==OGL_KEYBOARD_EVENT &&
	       (!focus_obj || focus_obj==select_hierarchy[i]))
	    {
		// first try with registered actions
		unsigned action, *params, selector_id;
		int n_params=selection_obj_params(select_hierarchy[i], &params);
		if(n_params) selector_id=params[0]; else selector_id=IS_SEL_NONE;
		if((action=event->action_search(select_hierarchy[i], selector_id))!=EM_ACTION_NONE)
		{
		    // only first matched action is processed in all objects of hierarchy until some returns true

		    // store action selector
		    event->set_action_last_selector(selector_id);
		    while(true)
		    {
			// there is a registered action for this object
			if(EM_GET_ACTION_TYPE(action)>EM_ACTION_TYPE_INT_LAST)
			    // user action
			    processed=action_user_dispatch(i, action);
			else
			{
			    // internal actions
			    action=event->action_internal_dispatch(action);
			    event->set_action_last(action);
			    processed=select_hierarchy[i]->event(this);
			    event->set_action_last(EM_ACTION_NONE);
			}

			if(focus_obj || processed || !i) break;

			// next loop
			i--;
		    }
		    return; // action processed
		}
	    }
	    else if(event->get_event_last()==OGL_MOUSE_MOVE_EVENT && focus_obj)
	    {
		// send action move events
		switch(EM_GET_ACTION_COORD(focus_action))
		{
		case EM_ACTION_COORD_NONE:
		    return;
		case EM_ACTION_COORD_NORMAL:
		    focus_coord[IS_X]=event->get_cursor(IS_X);
		    focus_coord[IS_Y]=event->get_cursor(IS_Y);
		    break;
		case EM_ACTION_COORD_DIFF:
		    focus_coord[IS_X]=event->get_cursor(IS_X)-focus_coord_bak[IS_X];
		    focus_coord[IS_Y]=event->get_cursor(IS_Y)-focus_coord_bak[IS_Y];
		    break;
		case EM_ACTION_COORD_DIFF_INF:
		    {
			int coord[IS_2D];
			if(!event->cursor_block(coord)) return; // no movement
			focus_coord[IS_X]+=coord[IS_X];
			focus_coord[IS_Y]+=coord[IS_Y];
		    }
		    break;
		case EM_ACTION_COORD_INC_INF:
		    {
			int coord[IS_2D];
			if(!event->cursor_block(coord)) return; // no movement
			focus_coord[IS_X]=coord[IS_X];
			focus_coord[IS_Y]=coord[IS_Y];
		    }
		    break;
		}
		unsigned action=EM_SET_ACTION_EVENT(focus_action, EM_ACTION_EVENT_MOVE);
		event->set_action_last(action);
		processed=focus_obj->event(this);
		event->set_action_last(EM_ACTION_NONE);
	    }

	    // pass event to object
	    if(!processed) processed=select_hierarchy[i]->event(this);

	    // stop sending events
	    if(processed) break;
	}
    }
}

bool render_mng::action_user_dispatch(int i, unsigned action)
{
    bool processed=false;

    switch(EM_GET_ACTION_SEQUENCE(action))
    {
    case EM_ACTION_SEQ_ONCE:
	if(!event->get_key_last_press())
	{
	    // send init
	    action=EM_SET_ACTION_EVENT(action, EM_ACTION_EVENT_INIT);
	    event->set_action_last(action);
	    processed=select_hierarchy[i]->event(this);
	    event->set_action_last(EM_ACTION_NONE);
	}
	else return true; // this action takes place in key release
	break;
    case EM_ACTION_SEQ_ONCE_PRESS:
	// TODO
	break;
    case EM_ACTION_SEQ_TWICE:
	if(!event->get_key_last_press())
	{
	    if(!focus_action)
	    {
		// disable keyboard repetition if commanded
		if(EM_GET_ACTION_KEY(action)==EM_ACTION_KEY_NO_REP)
		    w->get_ogl()->keyboard_repetition(false);
		// send focus event and activate focus
		focus_action=action;
		focus_obj=select_hierarchy[i];
		event_last=IS_STATUS_FOCUS_AC(EM_GET_ACTION_TYPE(action));
		select_hierarchy[i]->event(this);
		event_last=0;
		// send init
		action=EM_SET_ACTION_EVENT(action, EM_ACTION_EVENT_INIT);
		event->set_action_last(action);
		processed=select_hierarchy[i]->event(this);
		event->set_action_last(EM_ACTION_NONE);
		// restart coordinates
		focus_coord_bak[IS_X]=event->get_cursor(IS_X);
		focus_coord_bak[IS_Y]=event->get_cursor(IS_Y);
		switch(EM_GET_ACTION_COORD(focus_action))
		{
		case EM_ACTION_COORD_NONE:
		    break;
		case EM_ACTION_COORD_NORMAL:
		    focus_coord[IS_X]=event->get_cursor(IS_X);
		    focus_coord[IS_Y]=event->get_cursor(IS_Y);
		    break;
		case EM_ACTION_COORD_DIFF:
		    focus_coord[IS_X]=focus_coord[IS_Y]=0;
		    break;
		case EM_ACTION_COORD_DIFF_INF:
		case EM_ACTION_COORD_INC_INF:
		    event->cursor_block(NULL);
		    focus_coord[IS_X]=focus_coord[IS_Y]=0;
		    break;
		}
	    }
	    else
	    {
		// restore cursor to previous position
		if(EM_GET_ACTION_COORD(focus_action)==EM_ACTION_COORD_DIFF_INF ||
		   EM_GET_ACTION_COORD(focus_action)==EM_ACTION_COORD_INC_INF)
		    event->cursor_set_position(focus_coord_bak);
		// send finish
		action=EM_SET_ACTION_EVENT(action, EM_ACTION_EVENT_FINISH);
		event->set_action_last(action);
		processed=select_hierarchy[i]->event(this);
		event->set_action_last(EM_ACTION_NONE);
		// send focus event and remove focus
		event_last=IS_STATUS_FOCUS_AC(EM_GET_ACTION_TYPE(focus_action));
		select_hierarchy[i]->event(this);
		focus_action=event_last=0;
		focus_obj=NULL;
		// enable keyboard repetition if commanded
		if(EM_GET_ACTION_KEY(action)==EM_ACTION_KEY_NO_REP)
		    w->get_ogl()->keyboard_repetition(true);
		// disable vector actions
		event->action_vector_keyboard_reset();
	    }
	}
	else return true; // this action takes place in key release
	break;
    case EM_ACTION_SEQ_CANCEL:
	if(!event->get_key_last_press())
	{
	    if(!focus_action) ERROR("cancel action with no focus");
	    else
	    {
		// restore cursor to previous position
		if(EM_GET_ACTION_COORD(focus_action)==EM_ACTION_COORD_DIFF_INF ||
		   EM_GET_ACTION_COORD(focus_action)==EM_ACTION_COORD_INC_INF)
		    event->cursor_set_position(focus_coord_bak);
		// send cancel
		action=EM_SET_ACTION_EVENT(action, EM_ACTION_EVENT_CANCEL);
		event->set_action_last(action);
		processed=select_hierarchy[i]->event(this);
		event->set_action_last(EM_ACTION_NONE);
		// send focus event and remove focus
		event_last=IS_STATUS_FOCUS_AC(EM_GET_ACTION_TYPE(focus_action));
		select_hierarchy[i]->event(this);
		focus_action=event_last=0;
		focus_obj=NULL;
		// enable keyboard repetition if commanded
		if(EM_GET_ACTION_KEY(action)==EM_ACTION_KEY_NO_REP)
		    w->get_ogl()->keyboard_repetition(true);
		// disable vector actions
		event->action_vector_keyboard_reset();
	    }
	}
	else return true; // this action takes place in key release
	break;
    }
    return processed;
}

void render_mng::event_action_from_render_obj(unsigned action, unsigned obj_type, unsigned obj_status)
{
    // search all objects in render hierarchy from top down, and send them correcponding action
    event_last=0;
    event->set_action_last(action);
    for(int i=n_hierarchy-1; i>=0; i--)
    {
	if((obj_type==IS_TYPE_ANY || hierarchy[i]->get_type()==obj_type) &&
	   (obj_status==IS_STATUS_ANY || hierarchy[i]->get_status()==obj_status))
	    hierarchy[i]->event(this);
    }
    event->set_action_last(EM_ACTION_NONE);
}

void render_mng::select_render()
{
    pass=RENDER_PASS_SELECT;

    // set gl in select mode
    glSelectBuffer(MAX_SELECT_GL_BUFFER, select_gl_buffer);
    glRenderMode(GL_SELECT);
    glInitNames();

    root->render(this);

    // end selection mode
    n_select_gl_buffer=glRenderMode(GL_RENDER);

    // get selected object
    select_closer();

    // reset all calculations
    calc_all=0;

#if 0
    ERROR("Selection render %d", n_select_gl_buffer);
#endif
}

void render_mng::select_closer()
{
    // get closer valid selected object
    i_select_gl_buffer=-1;
    unsigned v_max=MAX_DEPTH;
    for(int sel=0, i=0; sel<n_select_gl_buffer; sel++)
    {
#if 0
	ERROR("%2u %15.12f", select_gl_buffer[i],
	      (float)select_gl_buffer[i+1]/(float)(1U<<31)*0.5);
#endif
	if(select_gl_buffer[i+1]<=v_max)
	{
	    i_select_gl_buffer=i;
	    v_max=select_gl_buffer[i+1];
	}
	i+=select_gl_buffer[i]+3;
    }
    select_depth_top=2.0*(double)v_max*(1.0/(double)MAX_DEPTH)-1;

    // extract select hierarchy (call events to notify obj status changes)
    // this will set IS_STATUS_SELECTED and IS_STATUS_CSELECTED in under-cursor objects
    int n=i_select_gl_buffer>=0?select_gl_buffer[i_select_gl_buffer]:0;  // number of names
    int old_select_hierarchy=n_select_hierarchy;
    n_select_hierarchy=0;
    for(int i=0; i<n; i++) // loop names
    {
	unsigned name=select_gl_buffer[i_select_gl_buffer+3+i];
	if(!(name&3))
	{
	    // this is a render_obj
	    render_obj *obj=(render_obj*)name;

	    if(n_select_hierarchy<old_select_hierarchy)
	    {
		if(select_hierarchy[n_select_hierarchy]!=obj)
		{
		    // changes in selection hierarchy
		    select_remove_selection_mark(n_select_hierarchy, old_select_hierarchy);
		    old_select_hierarchy=n_select_hierarchy;
		}
		else
		{
		    // hierarchy is being maintained
		    n_select_hierarchy++;
		    continue;
		}
	    }

	    int j=util::inc_vector(select_hierarchy, n_select_hierarchy, max_select_hierarchy, INC_HIERARCHY);
	    select_hierarchy[j]=obj;
	}
    }

    // remove old selection marks
    select_remove_selection_mark(n_select_hierarchy, old_select_hierarchy);

    // set new selection marks
    select_add_selection_mark(old_select_hierarchy, n_select_hierarchy);

    // convert between CSELECTED and SELECTED
    if(old_select_hierarchy>n_select_hierarchy) old_select_hierarchy=n_select_hierarchy;
    select_check_selection_mark(old_select_hierarchy-1);

#if 0
    for(int i=0; i<n_select_hierarchy; i++)
	ERROR("%p", select_hierarchy[i]);
#endif
}

void render_mng::select_check_selection_mark(int i)
{
    if(i>=0)
    {
	render_obj *obj=select_hierarchy[i];
	if(i<n_select_hierarchy-1)
	{
	    if(obj->get_status() & IS_STATUS_SELECTED)
	    {
		event_last=IS_STATUS_SELECTED | IS_STATUS_CSELECTED;
		obj->event(this);
	    }
	}
	else
	{
	    if(obj->get_status() & IS_STATUS_CSELECTED)
	    {
		event_last=IS_STATUS_SELECTED | IS_STATUS_CSELECTED;
		obj->event(this);
	    }
	}
    }
}

void render_mng::select_add_selection_mark(int from, int to)
{
    int i;
    event_last=IS_STATUS_CSELECTED;
    for(i=from; i<to-1; i++)
    {
	render_obj *obj=select_hierarchy[i];
	if(!(obj->get_status() & IS_STATUS_CSELECTED))
	    obj->event(this);
    }
    if(i==to-1)
    {
	event_last=IS_STATUS_SELECTED;
	render_obj *obj=select_hierarchy[i];
	if(!(obj->get_status() & IS_STATUS_SELECTED))
	    obj->event(this);
    }
}

void render_mng::select_remove_selection_mark(int from, int to)
{
    for(int i=from; i<to; i++)
    {
	render_obj *obj=select_hierarchy[i];
	if((event_last=(obj->get_status() & (IS_STATUS_SELECTED | IS_STATUS_CSELECTED))))
	    obj->event(this);
    }
}

int render_mng::selection_obj_params(render_obj *ro, unsigned **params)
{
    if(i_select_gl_buffer>=0)
    {
	int n=select_gl_buffer[i_select_gl_buffer];  // number of names
	for(int i=0; i<n; i++) // loop names searching ro
	{
	    render_obj *name=(render_obj*)select_gl_buffer[i_select_gl_buffer+3+i];
	    if(name==ro)
	    {
		// found
		i++;
		int n_params=0;
		*params=&select_gl_buffer[i_select_gl_buffer+3+i];
		while(i<n && (select_gl_buffer[i_select_gl_buffer+3+i]&IS_GL_NAME_PARAM))
		{
		    i++;
		    n_params++;
		}
		return n_params;
	    }
	}
    }

    *params=NULL;
    return 0;
}

unsigned render_mng::get_select_hierarchy_top_selector()
{
    if(i_select_gl_buffer>=0)
    {
	int n=select_gl_buffer[i_select_gl_buffer];  // number of names
	for(int i=n-1; i>=0; i--)
	{
	    unsigned name=select_gl_buffer[i_select_gl_buffer+3+i];
	    if((name&IS_GL_NAME_PARAM)==IS_GL_NAME_SELECTOR) return name;
	}
    }
    return IS_SEL_NONE;
}

void render_mng::set_stats(render_stats *stats)
{
    if(stats) stats->incref();
    if(stats_current) stats_current->decref();
    stats_current=stats;
}

void render_mng::set_forced_pass(render_pass p, render_obj *obj)
{
    // special case, only HUD required, but no auxiliary buffer -> force OPAQUE
    if(p==RENDER_PASS_HUD && !pass_hud_aux) p=RENDER_PASS_OPAQUE;

    root->set_hierarchical_forced_pass(p, obj);
}

double *render_mng::calculate_selection_modelview_matrix()
{
    if(!calc_modelview)
    {
	if(!n_select_hierarchy) return NULL;

	calc_modelview=1;

	// coef tricks
	int c_sel=0;
	double c2[IS_MAT4X4], *cc[2]={calc_modelview_matrix, c2};

	// init
	select_hierarchy[n_select_hierarchy-1]->get_modelview()->get_coefs_copy(cc[c_sel]);

	// multiply
	for(int i=n_select_hierarchy-2; i>=0; i--)
	{
	    select_hierarchy[i]->get_modelview()->matrix_mult(cc[c_sel], cc[c_sel^1]);
	    c_sel^=1;
	}
	if(c_sel) memcpy(calc_modelview_matrix, c2, IS_MAT4X4*sizeof(double));
    }
    return calc_modelview_matrix;
}

double *render_mng::calculate_hierarchy_modelview_nocam_matrix(double m[IS_MAT4X4])
{
    if(!n_hierarchy) return NULL;

    // coef tricks
    int c_sel=0;
    double c2[IS_MAT4X4], *cc[2]={m, c2};

    // init
    int i;
    for(i=n_hierarchy-1; i>=0; i--)
	if(hierarchy[i]->get_type()!=IS_TYPE_CAMERA)
	{
	    hierarchy[i]->get_modelview()->get_coefs_copy(cc[c_sel]);
	    break;
	}
    if(i<0) return NULL;
    i--;

    // multiply
    for(; i>=0; i--)
    {
	if(hierarchy[i]->get_type()!=IS_TYPE_CAMERA)
	{
	    hierarchy[i]->get_modelview()->matrix_mult(cc[c_sel], cc[c_sel^1]);
	    c_sel^=1;
	}
    }
    if(c_sel) memcpy(m, c2, IS_MAT4X4*sizeof(double));
    return m;
}

double *render_mng::calculate_selection_modelview_nocam_matrix()
{
    if(!calc_modelview_nocam)
    {
	if(!n_select_hierarchy) return NULL;

	// coef tricks
	int c_sel=0;
	double c2[IS_MAT4X4], *cc[2]={calc_modelview_nocam_matrix, c2};

	// init
	int i;
	for(i=n_select_hierarchy-1; i>=0; i--)
	    if(select_hierarchy[i]->get_type()!=IS_TYPE_CAMERA)
	    {
		select_hierarchy[i]->get_modelview()->get_coefs_copy(cc[c_sel]);
		break;
	    }
	if(i<0) return NULL;
	i--;

	calc_modelview_nocam=1;

	// multiply
	for(; i>=0; i--)
	{
	    if(select_hierarchy[i]->get_type()!=IS_TYPE_CAMERA)
	    {
		select_hierarchy[i]->get_modelview()->matrix_mult(cc[c_sel], cc[c_sel^1]);
		c_sel^=1;
	    }
	}
	if(c_sel) memcpy(calc_modelview_nocam_matrix, c2, IS_MAT4X4*sizeof(double));
    }
    return calc_modelview_nocam_matrix;
}

double *render_mng::calculate_selection_projection_matrix()
{
    if(!calc_projection)
    {
	if(!n_select_hierarchy) return NULL;

	// coef tricks
	int c_sel=0;
	double c2[IS_MAT4X4], *cc[2]={calc_projection_matrix, c2};

	// init
	int i;
	for(i=n_select_hierarchy-1; i>=0; i--)
	    if(select_hierarchy[i]->get_projection())
	    {
		select_hierarchy[i]->get_projection()->get_coefs_copy(cc[c_sel]);
		break;
	    }
	if(i<0) return NULL;
	i--;

	calc_projection=1;

	// multiply
	for(; i>=0; i--)
	{
	    if(select_hierarchy[i]->get_projection())
	    {
		select_hierarchy[i]->get_projection()->matrix_mult(cc[c_sel], cc[c_sel^1]);
		c_sel^=1;
	    }
	}
	if(c_sel) memcpy(calc_projection_matrix, c2, IS_MAT4X4*sizeof(double));
    }
    return calc_projection_matrix;
}

double *render_mng::calculate_selection_inv_modelview_matrix()
{
    if(!calc_inv_modelview)
    {
	double *m=calculate_selection_modelview_matrix();
	if(!m) return NULL;

	// calculate inverse modelview
	if(!matrix4x4::invert(m, calc_inv_modelview_matrix)) ERROR_RX(NULL, "modelview not inversible");

	calc_inv_modelview=1;
    }
    return calc_inv_modelview_matrix;
}

double *render_mng::calculate_selection_inv_projection_matrix()
{
    if(!calc_inv_projection)
    {
	double *m=calculate_selection_projection_matrix();
	if(!m) return NULL;

	// calculate inverse projection
	if(!matrix4x4::invert(m, calc_inv_projection_matrix)) ERROR_RX(NULL, "projection not inversible");

	calc_inv_projection=1;
    }
    return calc_inv_projection_matrix;
}

double *render_mng::calculate_selection_inv_utils()
{
    // check frame
    if(!obj_frame) ERROR_RX(NULL, "no frame in calculation");

    if(!calc_inv_utils)
    {
	double *m=calculate_selection_projection_matrix();
	if(!m) return NULL;

	calc_inv_utils=1;

	calc_inv_utils_data[0]=2.0/(double)obj_frame->get_viewport(IS_WW);
	calc_inv_utils_data[1]=2.0/(double)obj_frame->get_viewport(IS_WH);
	calc_inv_utils_data[2]=m[10];
	calc_inv_utils_data[3]=m[14];
    }
    return calc_inv_utils_data;
}

void *render_mng::get_action_params(int size)
{
    if(size>max_action_params)
    {
	max_action_params=size;
	action_params=realloc(action_params, max_action_params);
    }
    return action_params;
}

void render_mng::get_selection_touch_point(double v[IS_3D])
{
    // inversion utils
    double *utl=calculate_selection_inv_utils();

    // from window coord to open gl [-1, 1] coords
    double cube[IS_4D];
    cube[IS_X]=(double)event->get_cursor(IS_WX)*utl[0]-1.0;
    cube[IS_Y]=(double)event->get_cursor(IS_WY)*utl[1]-1.0;
    cube[IS_Z]=get_select_depth_top();

    // calculate projection vector
    cube[IS_W]= utl[3]/(cube[IS_Z]+utl[2]);
    cube[IS_X]*=cube[IS_W];
    cube[IS_Y]*=cube[IS_W];
    cube[IS_Z]*=cube[IS_W];

    // to object coordinates
    double proj[IS_4D];
    matrix4x4::vect_mult      (calculate_selection_inv_projection_matrix(), cube, proj);
    matrix4x4::vect_mult_3x3_1(calculate_selection_inv_modelview_matrix(),  proj, v);
}

